import * as msal from '@azure/msal-browser'
import { CacheUtils } from '../cache';
import { ACCESS_TOKEN_CAHCHE_KEY, AUTH_DIRECT_URL, BACKUP_DIR_NAME, BACKUP_NAME } from './conf';
import { AuthProviderCallback, Client } from "@microsoft/microsoft-graph-client";

const clientId = '77a52b9b-3b31-496e-aacc-307b60395beb'
// const tenantId = '61458fde-e468-4dce-9880-106f073f13b7'
const API_SCOPE = ['Files.Read', 'Files.ReadWrite', 'Files.Read.All']
export class OneDriveUtils implements BackupDriver {

    name = 'onedrive';
    app?: msal.IPublicClientApplication
    public CACHE_KEY = 'oneDrive-'
    public getCacheKey() {
        return this.CACHE_KEY + ACCESS_TOKEN_CAHCHE_KEY
    }
    getRedirectUri() {
        return chrome?.identity ? chrome.identity.getRedirectURL() : AUTH_DIRECT_URL
    }
    async init() {
        this.app = await msal.createStandardPublicClientApplication({
            auth: {
                clientId,
                authority: `https://login.microsoftonline.com/consumers`,
                redirectUri: this.getRedirectUri(),
            }
        })
        this.app?.initialize()
    }
    public async cancelAuth() {
        const url = await this.getLogoutUrl()
        await CacheUtils.remove(this.getCacheKey())
        await this.launchWebAuthFlow(url);
    }
    async getToken(hash: string) {
        if (!this.app) {
            throw new Error('app not init')
        }
        const params = new URLSearchParams(hash)
        const code = params.get('code')
        if (!code) {
            throw new Error('code not found')
        }
        return code
    }
    async getCode(hash: string) {
        const params = new URLSearchParams(hash)
        return params.get('code')
    }

    getTokenExpires(hash: string) {
        const params = new URLSearchParams(hash)
        return params.get('expires_in')
    }
    async auth(): Promise<string> {
        if (!this.app) {
            throw new Error('app not init')
        }
        await this.app?.clearCache()
        const res = await this.acquireToken()
        if (!res) {
            throw new Error('auth failed')
        }
        const expiresOn = (res.expiresOn?.getTime() || 0) - Date.now()
        window.postMessage({ type: 'backupAuthOk', token: res.accessToken, expires: expiresOn > 0 ? expiresOn : undefined })
        return ''
    }
    getTokenWithCode(_code: string, _hash: string): Promise<string | null | undefined> {
        throw new Error("Method not implemented.");
    }
    async launchWebAuthFlow(url: string): Promise<msal.AuthenticationResult | undefined | null> {
        return new Promise((resolve, reject) => {
            chrome.identity.launchWebAuthFlow({
                interactive: true,
                url
            }, (responseUrl) => {
                if (!this.app) {
                    throw new Error('app not init')
                }
                // Response urls includes a hash (login, acquire token calls)
                if (responseUrl?.includes("#")) {
                    this.app.handleRedirectPromise(`#${responseUrl.split("#")[1]}`)
                        .then(resolve)
                        .catch(reject)
                } else {
                    // Logout calls
                    reject(Error('no hash'));
                }
            })
        })
    }
    async getAcquireTokenUrl(): Promise<string> {
        return new Promise((resolve, reject) => {
            if (!this.app) {
                throw new Error('app not init')
            }
            this.app.acquireTokenRedirect({
                scopes: API_SCOPE,
                redirectUri: this.getRedirectUri(),
                prompt: 'select_account',
                onRedirectNavigate: (url) => {
                    resolve(url);
                    return false;
                }
            }).catch(reject);
        });
    }
    async getLogoutUrl(): Promise<string> {
        return new Promise((resolve, reject) => {
            if (!this.app) {
                throw new Error('app not init')
            }
            this.app.logout({
                onRedirectNavigate: (url) => {
                    resolve(url);
                    return false;
                }
            }).catch(reject);
        });
    }
    async acquireToken(): Promise<msal.AuthenticationResult | undefined | null> {
        if (!this.app) {
            throw new Error('app not init')
        }
        const res = await this.app.acquireTokenSilent({
            scopes: API_SCOPE,
            account: this.app.getAllAccounts()[0],
        }).catch(() => undefined)
        if (res) {
            return res
        }
        const acquireTokenUrl = await this.getAcquireTokenUrl();
        return this.launchWebAuthFlow(acquireTokenUrl);
    }
    async save(file: Blob) {
        const accessToken: string = await CacheUtils.get(this.getCacheKey())
        const client = Client.init({
            authProvider: function (done: AuthProviderCallback): void {
                done(null, accessToken)
            }
        })
        const meta = await client.api(`/me/drive/root:/${BACKUP_DIR_NAME}/${BACKUP_NAME}`).get().catch(() => undefined)
        const root = await client.api(`/me/drive/root:/${BACKUP_DIR_NAME}`).get().catch(() => undefined)
        if (!meta) {
            //新建
            if (!root) {
                await client.api(`/me/drive/items/root/children`).post({
                    "name": BACKUP_DIR_NAME,
                    "folder": {},
                    "@microsoft.graph.conflictBehavior": "rename"
                })
            }
            await client.api(`/me/drive/root:/${BACKUP_DIR_NAME}/${BACKUP_NAME}:/content`).put(file)
        } else {
            await client.api(`/me/drive/items/${meta.id}/content`).put(file)
        }
    }
    async load(): Promise<BackupFile | undefined> {
        const accessToken: string = await CacheUtils.get(this.getCacheKey())
        const client = Client.init({
            authProvider: function (done: AuthProviderCallback): void {
                done(null, accessToken)
            }
        })
        try {
            const meta = await client.api(`/me/drive/root:/${BACKUP_DIR_NAME}/${BACKUP_NAME}`).get()
            const file:ReadableStream = await client.api(`/me/drive/root:/${BACKUP_DIR_NAME}/${BACKUP_NAME}:/content`).get()
            const response = new Response(file)
            return {
                id: meta.id,
                file: await response.blob()
            }
        } catch (error) {
            console.log(error)
        }
    }
    async isAuth() {
        const accessToken = await CacheUtils.get(this.getCacheKey())
        return !!accessToken
    }
    async delete() {
        //DELETE 
        // const accessToken: string = await CacheUtils.get(this.getCacheKey())
        // const client = Client.init({
        //     authProvider: function (done: AuthProviderCallback): void {
        //         done(null, accessToken)
        //     }
        // })
        // await client.api(`/drives/{drive-id}/items/${BACKUP_NAME}/permanentDelete`).delete()
    }
}